#include "Script.h"
#include "Unit.h"
#include "SynthUnits.h"
#include "LV2Unit.h"
#include "UnitManager.h"

using namespace std;

s7_pointer scUnitName(s7_scheme *s7, s7_pointer args)
{
   if (!s7_is_c_pointer(s7_car(args)))
   {
      s7_wrong_type_arg_error(s7, "unit-name", 0, s7_car(args), "Unit");
      return s7_unspecified(s7);
   }

   Unit *unit = (Unit*) s7_c_pointer(s7_car(args));
   return s7_make_string(s7, unit->getName().c_str());
}

s7_pointer scListUnits(s7_scheme *s7, s7_pointer args)
{
   list<Unit*> units = UnitManager::getInstance().allUnits();

   s7_pointer lst = s7_nil(s7);

   for (list<Unit*>::iterator i = units.begin();
        i != units.end();
        i ++)
   {
      lst = s7_cons(s7, s7_make_c_pointer(s7, (*i)), lst);
   }

   return lst;
}

s7_pointer scListPorts(s7_scheme *s7, s7_pointer args)
{
   if (!s7_is_c_pointer(s7_car(args)))
   {
      s7_wrong_type_arg_error(s7, "list-ports", 0, s7_car(args), "Unit");
      return s7_unspecified(s7);
   }

   Unit *unit = (Unit*) s7_c_pointer(s7_car(args));
   list<Port*> ports = unit->getPorts();
   s7_pointer lst = s7_nil(s7);

   int id = 0;
   for (list<Port*>::iterator i = ports.begin(); i != ports.end(); i ++)
   {
      s7_pointer pair = s7_cons(s7, s7_make_integer(s7, id), s7_make_string(s7, (*i)->getName().c_str()));
      lst = s7_cons(s7, pair, lst);
      id ++;
   }

   return lst;
}

s7_pointer scMkSinOsc(s7_scheme *s7, s7_pointer args)
{
   s7_pointer freq = s7_car(args);
   args = s7_cdr(args);
   s7_pointer phase = s7_car(args);
   args = s7_cdr(args);
   s7_pointer name = s7_car(args);

   /* Check the arguments */
   if (!s7_is_number(freq))
   {
      s7_wrong_type_arg_error(s7, "Sin", 0, freq, "number");
      return s7_unspecified(s7);
   }
   if (!s7_is_number(phase))
   {
      s7_wrong_type_arg_error(s7, "Sin", 1, phase, "number");
      return s7_unspecified(s7);
   }
   if (!s7_is_string(name))
   {
      s7_wrong_type_arg_error(s7, "Sin", 2, name, "string");
      return s7_unspecified(s7);
   }

   /* Create the oscilator */
   SinOsc *unit = new SinOsc();
   unit->setFreq(s7_number_to_real(s7, freq));

   /* See if a name is given */
   unit->setName(string(s7_string(name)));

   UnitManager::getInstance().registerUnit(unit);
   
   DEBUG("Created SinOsc with name=\"%s\" frequency=%f\n", unit->getName().c_str(), unit->getFreq());
   return s7_make_c_pointer(s7, (void*) unit);
}

s7_pointer scMkGain(s7_scheme *s7, s7_pointer args)
{
   s7_pointer gain = s7_car(args);
   s7_pointer name = s7_cadr(args);

   /* Check the arguments */
   if (!s7_is_number(gain))
   {
      s7_wrong_type_arg_error(s7, "Gain", 0, gain, "number");
      return s7_unspecified(s7);
   }
   if (!s7_is_string(name))
   {
      s7_wrong_type_arg_error(s7, "Gain", 1, name, "string");
      return s7_unspecified(s7);
   }
   
   /* Create the unit */
   try
   {
      Gain *unit = new Gain();
      unit->setGain(s7_number_to_real(s7, gain));
      unit->setName(string(s7_string(name)));

      UnitManager::getInstance().registerUnit(unit);
      DEBUG("Created Gain with name=\"%s\" gain=%f\n", unit->getName().c_str(), unit->getGain());

      return s7_make_c_pointer(s7, (void*) unit);
   }
   catch(exception &e)
   {
      return s7_unspecified(s7);
   }
}

s7_pointer scMakeLv2(s7_scheme *s7, s7_pointer args)
{
   LV2Unit *unit;
   s7_pointer path = s7_car(args);
   args = s7_cdr(args);
   s7_pointer name = s7_car(args);

   if (!s7_is_string(path))
   {
      s7_wrong_type_arg_error(s7, "LV2", 0, path, "path");
      return s7_unspecified(s7);
   }
   if (!s7_is_string(name))
   {
      s7_wrong_type_arg_error(s7, "LV2", 1, name, "string");
      return s7_unspecified(s7);
   }

   try
   {
      unit = new LV2Unit();
      unit->setName(string(s7_string(name)));
      unit->load(string(s7_string(path)));
      UnitManager::getInstance().registerUnit(unit);
   }
   catch (exception &e)
   {
      DEBUG("Exception: %s\n", e.what());
      return s7_unspecified(s7);
   }

   return s7_make_c_pointer(s7, (void*) unit);
}

s7_pointer scTestAr(s7_scheme *s7, s7_pointer args)
{
   /* Check arguments */
   if (!s7_is_c_pointer(s7_car(args)))
   {
      s7_wrong_type_arg_error(s7, "test-ar", 0, s7_car(args), "Unit");
      return s7_unspecified(s7);
   }

   Unit *unit = (Unit*) s7_c_pointer(s7_car(args));
   sample_t buf[20] = {0};
   unit->ar(0, buf, 20, Options().setIsDebugBuffer(true));
   return s7_unspecified(s7);
}

s7_pointer scConnections(s7_scheme *s7, s7_pointer args)
{
   if (!s7_is_c_pointer(s7_car(args)))
   {
      s7_wrong_type_arg_error(s7, "connections", 0, s7_car(args), "Unit");
      return s7_unspecified(s7);
   }
   if (!s7_is_integer(s7_cadr(args)))
   {
      s7_wrong_type_arg_error(s7, "connections", 1, s7_cadr(args), "Integer");
      return s7_unspecified(s7);
   }

   Unit *unit = (Unit*) s7_c_pointer(s7_car(args));

   unsigned port = (unsigned) s7_integer(s7_cadr(args));
   if (port < 0 || port >= unit->getAudioPortCount())
   {
      DEBUG("Port number is invalid");
      return s7_unspecified(s7);
   }

   list<Connection*>* connections = unit->getConnections((unsigned) s7_integer(s7_cadr(args)));

   s7_pointer lst = s7_nil(s7);

   for (list<Connection*>::iterator i = connections->begin();
        i != connections->end();
        i ++)
   {
      lst = s7_cons(s7, s7_make_c_pointer(s7, (*i)->getUnit()), lst);
   }

   return lst;
}

s7_pointer scConnect(s7_scheme *s7, s7_pointer args)
{
   int srcPort = -1, dstPort = -1;
   Unit *srcUnit = NULL, *dstUnit = NULL;

   for (s7_pointer r = args; !s7_is_null(s7, r); r = s7_cdr(r))
   {
      if (s7_is_c_pointer(s7_car(r)))
      {
         if (srcUnit == NULL)
            srcUnit = (Unit*) s7_c_pointer(s7_car(r));
         else
         {
            dstUnit = (Unit*) s7_c_pointer(s7_car(r));

            connect(srcUnit, dstUnit, (srcPort != -1) ? srcPort : 0, (dstPort != -1) ? dstPort : 0);
            DEBUG("Connect unit(%p:%d) to unit(%p:%d)\n",
                  srcUnit, (srcPort != -1) ? srcPort : 0,
                  dstUnit, (dstPort != -1) ? dstPort : 0);

            srcUnit = dstUnit;
            srcPort = dstPort = -1;
         }
      }
      else if (s7_is_integer(s7_car(r)))
      {
         if (srcPort == -1)
            srcPort = s7_integer(s7_car(r));
         else
            dstPort = s7_integer(s7_car(r));
      }
   }

   return s7_make_c_pointer(s7, (void*) srcUnit);
}


/* Set up interface functions */
void Script::registerNatives()
{
   s7_define_constant_with_documentation(s7, "dac", s7_make_c_pointer(s7, (void*) UnitManager::getInstance().getDac()),
         "DAC (Digital to Audio Converter) is a system unit representing the system output.");

   s7_define_function(s7, "unit-name", scUnitName, 1, 0, 0, "(unit-name Unit) --> unit-name");
   s7_define_function(s7, "list-units", scListUnits, 0, 0, 0, "(list-units) --> list");
   s7_define_function(s7, "list-ports", scListPorts, 1, 0, 0, "(list-ports Unit) --> list");
   s7_define_function_star(s7, "Sin", scMkSinOsc, "(freq 440) (phase 0) (name \"\")", "(Sin frequency :optional name)\nCreate a SinOsc unit");
   s7_define_function_star(s7, "Gain", scMkGain, "(gain 1) (name \"\")", "(Gain gain name)\nCreate a Gain unit");
   s7_define_function_star(s7, "LV2", scMakeLv2, "(path \"\") (name \"\")", "(LV2 file-name [unit-name])");
   s7_define_function(s7, "test-ar", scTestAr, 1, 0, 0, "(test-ar unit)\nTest run of ar() of a Unit");
   s7_define_function(s7, "connect", scConnect, 2, 0, 1, "(connect source-unit [output-port] [input-port] destination-unit ...)\n"
                                                         "Connect a chain of units");
   s7_define_function(s7, "connections", scConnections, 2, 0, 0, "(connections Unit) --> list\n");
}
